#include "DirectX11.h"
#include "Renderable.h"
#include "View.h"

#define SAFE_RELEASE(ptr) if(ptr){ ptr->Release(); ptr = nullptr;}

#define TESTACTIVE 0


//Move to Renderable
#include "VertexShader.csh"
#include "PixelShader.csh"
#include "HelperD3D.h"


DirectX11* DirectX11::instance = nullptr;

DirectX11* DirectX11::GetInstance(){
	if(instance == nullptr)
		instance = new DirectX11();

	return instance;
}

void DirectX11::DeleteInstance(){

	delete instance;
	instance = nullptr;

}



DirectX11::DirectX11(void){}
DirectX11::~DirectX11(void){}

bool DirectX11::Initialize(HWND hWnd){

	//Initialize COM pointers
	device.CoCreateInstance(__uuidof(ID3D11Device));
	deviceContext.CoCreateInstance(__uuidof(ID3D11DeviceContext));
	rtv.CoCreateInstance(__uuidof(ID3D11RenderTargetView));
	swapChain.CoCreateInstance(__uuidof(IDXGISwapChain));

	//Create Device And Swap chain
	DXGI_SWAP_CHAIN_DESC scDesc;
	ZeroMemory(&scDesc, sizeof(DXGI_SWAP_CHAIN_DESC));		//Clear memory to 0
	scDesc.BufferCount = 1;
	scDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	scDesc.BufferDesc.Width = WINDOW_WIDTH;
	scDesc.BufferDesc.Height = WINDOW_HEIGHT;
	scDesc.BufferDesc.RefreshRate.Numerator = 60;
	scDesc.BufferDesc.RefreshRate.Denominator = 1;
	scDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	scDesc.OutputWindow = hWnd;
	scDesc.SampleDesc.Count = 1;
	scDesc.SampleDesc.Quality = 0;
	scDesc.Windowed = TRUE;

	//Set D3D Feature Level
	D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0;
	D3D_FEATURE_LEVEL featureLevelsSupported;
	UINT numFeatureLevels = 1;

	HRESULT hr;

#if _DEBUG
	hr = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE,NULL,
		D3D11_CREATE_DEVICE_DEBUG,&featureLevel,numFeatureLevels,D3D11_SDK_VERSION,
		&scDesc,&swapChain,&device,&featureLevelsSupported,&deviceContext);
#else
	hr = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE,NULL,
		NULL,&featureLevel,numFeatureLevels,D3D11_SDK_VERSION,
		&scDesc,&swapChain,&device,&featureLevelsSupported,&deviceContext);
#endif

	if(FAILED(hr)){
		ERRORMESSAGE(L"ERROR: Failed to create the Device and Swap Chain");
		//return false;
	}

	hr = device->CreateDeferredContext(0, &deferredContext);
	ERRORCHECK(hr, L"ERROR: Failed to create the deferred context");

	//Create Depth Stencil View
	CComPtr<ID3D11Texture2D> zBuffer;

	D3D11_TEXTURE2D_DESC dDesc;
	ZeroMemory(&dDesc,sizeof(D3D11_TEXTURE2D_DESC));
	dDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	dDesc.Format = DXGI_FORMAT_D32_FLOAT;
	dDesc.Width = WINDOW_WIDTH;
	dDesc.Height = WINDOW_HEIGHT;
	dDesc.MipLevels = 1;
	dDesc.ArraySize = 1;
	dDesc.SampleDesc.Count = 1;
	dDesc.SampleDesc.Quality = 0;
	dDesc.Usage = D3D11_USAGE_DEFAULT;
	dDesc.CPUAccessFlags = NULL;
	dDesc.MiscFlags = 0;

	hr = device->CreateTexture2D(&dDesc,NULL,&zBuffer.p);

	if(FAILED(hr))
		ERRORMESSAGE(L"ERROR: Failed to create the zBuffer");
	//return false;

	D3D11_TEX2D_DSV texdsv;
	texdsv.MipSlice = 0;

	D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc;
	ZeroMemory(&dsvDesc,sizeof(D3D11_DEPTH_STENCIL_VIEW_DESC));
	dsvDesc.Flags = 0;
	dsvDesc.Format = dDesc.Format;
	dsvDesc.Texture2D = texdsv;
	dsvDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;

	hr = device->CreateDepthStencilView(zBuffer,&dsvDesc,&dsv);

	if(FAILED(hr))
		ERRORMESSAGE(L"ERROR: Failed to create the DSV.");
	//return false;

	CComPtr<ID3D11Texture2D> backbuffer;
	hr = swapChain->GetBuffer(0,__uuidof(ID3D11Texture2D),(LPVOID*) &backbuffer);

	if(FAILED(hr))
		ERRORMESSAGE(L"ERROR: Failed to get the backbuffer.");

	hr = device->CreateRenderTargetView(backbuffer,NULL,&rtv);
	ERRORCHECK(hr,L"ERROR: Failed to create RTV.");

	D3D11_TEXTURE2D_DESC bbDesc;
	backbuffer->GetDesc(&bbDesc);
	viewport.Height = (FLOAT)bbDesc.Height;
	viewport.Width = (FLOAT)bbDesc.Width;
	viewport.MinDepth = 0;
	viewport.MaxDepth = 1;
	viewport.TopLeftX = 0;
	viewport.TopLeftY = 0;

	for(int i = 0; i < NUMCONSTBUFFS; i++)
		constantBuffers[i] = nullptr;

#if TESTACTIVE
	//Create Shaders... TO Be moved to Renderable 
	hr = device->CreateVertexShader(VertexShader, sizeof(VertexShader), NULL, &vs);
	ERRORCHECK(hr, L"ERROR: Failed to Create Vertex Shader\n\tMay be due to problem in shader"); 

	hr = device->CreatePixelShader(PixelShader, sizeof(PixelShader), NULL, &ps);
	ERRORCHECK(hr, L"ERROR: Failed to Create Vertex Shader\n\tMay be due to problem in shader");

	//Create Input Layouts...
	D3D11_INPUT_ELEMENT_DESC layout[] = {
		{ LPCSTR("POSITION"),0,DXGI_FORMAT_R32G32B32_FLOAT,0,D3D11_APPEND_ALIGNED_ELEMENT,D3D11_INPUT_PER_VERTEX_DATA },
		{ LPCSTR("NORMAL"), 0, DXGI_FORMAT_R32G32B32_FLOAT, 1, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA },
		{ LPCSTR("TEXCOORD"), 0, DXGI_FORMAT_R32G32_FLOAT, 2, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA },
		{ LPCSTR("TANGENT"), 0, DXGI_FORMAT_R32G32_FLOAT, 3, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA }
	};

	hr = device->CreateInputLayout(layout, ARRAYSIZE(layout), VertexShader, sizeof(VertexShader), &inputLayout);
	ERRORCHECK(hr, L"ERROR: Failed to create input layout");

	//Create Buffers...
	D3D11_BUFFER_DESC desc;
	ZeroMemory(&desc, sizeof(D3D11_BUFFER_DESC));
	desc.Usage = D3D11_USAGE_DYNAMIC;
	desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	desc.MiscFlags = 0;
	desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	desc.ByteWidth = sizeof(OBJECT);
	desc.StructureByteStride = 0;

	D3D11_SUBRESOURCE_DATA init_data;
	ZeroMemory(&init_data, sizeof(D3D11_SUBRESOURCE_DATA));
	init_data.pSysMem = &object;
	init_data.SysMemPitch = 0;
	init_data.SysMemSlicePitch = 0;

	hr = device->CreateBuffer(&desc,&init_data,&bObjectCB);
	ERRORCHECK(hr, L"ERROR: Failed to create Object CB");

	desc.ByteWidth = sizeof(SCENE);
	init_data.pSysMem = &scene;

	hr = device->CreateBuffer(&desc, &init_data, &bSceneCB);
	ERRORCHECK(hr, L"ERROR: Failed to create Object CB");

	XMStoreFloat4x4(&object.mWorld, XMMatrixIdentity());


	D3D11_BUFFER_DESC desc;
	ZeroMemory(&desc, sizeof(D3D11_BUFFER_DESC));
	desc.Usage = D3D11_USAGE_DYNAMIC;
	desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	desc.MiscFlags = 0;
	desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	desc.ByteWidth = sizeof(LIGHT);
	desc.StructureByteStride = 0;

	D3D11_SUBRESOURCE_DATA init_data;
	ZeroMemory(&init_data, sizeof(D3D11_SUBRESOURCE_DATA));
	init_data.pSysMem = &light;
	init_data.SysMemPitch = 0;
	init_data.SysMemSlicePitch = 0;

	hr = device->CreateBuffer(&desc,&init_data,&bLightCB);
	ERRORCHECK(hr, L"ERROR: Failed to create Light CB");


	light.color = XMFLOAT4(1.0f,0.1f,0.1f,1.0f);
#endif

	return true;
}

bool DirectX11::Shutdown(){
	DeleteInstance();		
	return true;
}

void DirectX11::Draw(Renderable* object,View* camera){

	RenderInfo info = object->GetRenderInformation();
	D3D11_VIEWPORT vp = camera->GetViewPort();
	ID3D11Buffer* sceneBuffer = camera->GetBuffer();
	SCENE sScene = *camera->GetStruct();

	deferredContext->OMSetRenderTargets(1, &rtv.p, dsv);
	deferredContext->RSSetViewports(1, &viewport);

	deferredContext->IASetVertexBuffers(0, info.numBuffers, info.buffers, info.strides, info.offsets);
	deferredContext->IASetIndexBuffer(info.indexBuffer, DXGI_FORMAT_R32_UINT, 0);
	deferredContext->IASetInputLayout(info.inputLayout);
	deferredContext->IASetPrimitiveTopology(info.topology);
	deferredContext->VSSetShader(info.vs, NULL, NULL);
	deferredContext->PSSetShader(info.ps, NULL, NULL);
	if(info.srv)
		deferredContext->PSSetShaderResources(0,1,&info.srv);
	if(info.normalMapSRV)
		deferredContext->PSSetShaderResources(1,1,&info.normalMapSRV);
	if(info.samplerState)
		deferredContext->PSSetSamplers(0,1,&info.samplerState);

	SetConstantBuffer(&sceneBuffer,CB_CAMERA);
	SetConstantBuffer(&info.objectCB,CB_OBJECT);

	deferredContext->VSSetConstantBuffers(0, NUMCONSTBUFFS, constantBuffers);
	deferredContext->PSSetConstantBuffers(0, NUMCONSTBUFFS, constantBuffers);

	D3D11_MAPPED_SUBRESOURCE msr1;
	deferredContext->Map(info.objectCB, NULL, D3D11_MAP_WRITE_DISCARD, 0, &msr1);
	memcpy(msr1.pData, &info.object, sizeof(OBJECT));
	deferredContext->Unmap(info.objectCB, 0);

	D3D11_MAPPED_SUBRESOURCE msr;
	deferredContext->Map(sceneBuffer, NULL, D3D11_MAP_WRITE_DISCARD, 0, &msr);
	memcpy(msr.pData, &sScene, sizeof(SCENE));
	deferredContext->Unmap(sceneBuffer, 0);

	deferredContext->DrawIndexed(info.numIndices, 0, 0);
}

bool DirectX11::Render(){

	FLOAT color[4] = { 0.75f, 0.4f, 0.5f, 1.0f };
	deviceContext->ClearRenderTargetView(rtv.p,color);
	deviceContext->ClearDepthStencilView(dsv.p, D3D11_CLEAR_DEPTH, FAR_PLANE, 0);

	deferredContext->FinishCommandList(FALSE,&commandList);
	deviceContext->ExecuteCommandList(commandList,FALSE);
	commandList.Release();


	swapChain->Present(0,0);
	return true;
}

void DirectX11::SetConstantBuffer(ID3D11Buffer** buffer,ConstBuffSlots slot)
{
	constantBuffers[slot] = *buffer;
}



